package com.btpn.custody.service.impl;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.btpn.custody.bean.BranchEmailSender;
import com.btpn.custody.bean.ParameterBeans;
import com.btpn.custody.bean.RelateBranchJob;
import com.btpn.custody.common.Constant;
import com.btpn.custody.dao.IAccount;
import com.btpn.custody.dao.IBranch;
import com.btpn.custody.dao.IMasterData;
import com.btpn.custody.dao.ITL_MD;
import com.btpn.custody.dao.IUser;
import com.btpn.custody.entity.Account;
import com.btpn.custody.entity.Branch;
import com.btpn.custody.entity.MasterData;
import com.btpn.custody.entity.TL_MD;
import com.btpn.custody.entity.User;
import com.btpn.custody.service.IMasterService;
import com.btpn.custody.web.m003.model.Report01Model;
import com.btpn.custody.web.m003.model.Report04Model;
import com.btpn.custody.web.m003.model.Report05Model;

@Service("masterDataService")
@Transactional
public class MasterServiceImpl implements IMasterService {

	private final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(this.getClass());

	@Autowired
	private IBranch branchDaoHibernate;

	@Autowired
	private BranchEmailSender branchEmailSender;

	@Autowired
	private IAccount accountDaoHibernate;

	@Autowired
	private IMasterData masterDataDaoHibernate;

	@Autowired
	private ITL_MD TL_MD_DaoHibernate;

	@Autowired
	private ParameterBeans parameterBeans;

	@Autowired
	private IUser userDaoHibernate;

	@Value("${THREAD_DL}")int threadSum;

	private ExecutorService executor;
	private List<Future<HashMap<String, Integer>>> futures;
	
	public String relateWithBranch(final String user, Date fromDate, Date toDate) {

		/*
		 * get MasterDate where STATUS=NEW
		 * For STATUS=FAIL init when add branch
		 */


	    
//		List<MasterData> masterDatas = masterDataDaoHibernate.findByStatuses(Constant.STATUS__MD_NEW, Constant.STATUS__MD_FAIL, fromDate, toDate);
		List<MasterData> masterDatas = masterDataDaoHibernate.findByAccount(Constant.STATUS__MD_NEW, Constant.STATUS__MD_FAIL, fromDate, toDate);
		System.out.println("===============>masterDatas size: "+masterDatas.size());
		
//		List<MasterData> masterDatas = masterDataDaoHibernate.findBySIDStatuses(Constant.STATUS__MD_NEW, Constant.STATUS__MD_FAIL,"12a35869-9f96-47e0-9f10-0b4b788ba235");

//	List<MasterData> masterDatas = masterDataDaoHibernate.findByStatuses(Constant.STATUS__MD_NEW, Constant.STATUS__MD_FAIL, utilDate, utilDate);
//		List<MasterData> masterDatas = masterDataDaoHibernate.findByStatus(Constant.STATUS__MD_NEW);
		logger.info("Found {} MDs", masterDatas.size());

		int c1_blankCode=0;
		int c1_branchNotFound=0;
		int c1_related=0;
		int c1=0;

// VERSI : multithread, but transaction just support 1 connection so time always same

//		List<String> masterDatas = new ArrayList<String>();
//		for (int j=0; j<18; j++) {
//			masterDatas.add(String.valueOf(j));
//		}
//logger.debug("SIZE: "+masterDatas.size());

		int threadInLoop=threadSum;
		int size=masterDatas.size();
		int per=size/threadInLoop;
		int mod=size%threadInLoop;
		executor = Executors.newFixedThreadPool(threadInLoop);
		futures = new ArrayList<Future<HashMap<String, Integer>>>();

		if (mod!=0) {
			threadInLoop--;
			int from = threadInLoop*per;
			int to = size-1;
			Callable<HashMap<String, Integer>> worker = new RelateBranchJob(user, masterDatas, from, to);
	        Future<HashMap<String, Integer>> submit = executor.submit(worker);
	        futures.add(submit);

//	        System.out.println("SIZE: "+size+" thread: "+threadInLoop+ " SISA: "+mod);
//			processCallable(threadInLoop*per, size-1, masterDatas, user);
//			processCallable(threadInLoop*per, size-1, masterDatas);
		}

		for (int i=0; i<threadInLoop; i++) {
			int from=i*per;
			int to=(i+1)*per-1;
			Callable<HashMap<String, Integer>> worker = new RelateBranchJob(user, masterDatas, from, to);
	        Future<HashMap<String, Integer>> submit = executor.submit(worker);
	        futures.add(submit);

//			processCallable(from, to, masterDatas, user);
//			processCallable(from, to, masterDatas);
		}


        // retrieve the result
		HashMap<String, Integer> hasil;
		for (Future<HashMap<String, Integer>> future : futures) {
			try {
				hasil = future.get();
				c1_blankCode += hasil.get("c_blankCode");
				c1_branchNotFound += hasil.get("c_branchNotFound");
				c1_related += hasil.get("c_related");
				c1 += hasil.get("c");
				logger.debug("c1_blankCode:"+c1_blankCode+", c1_branchNotFound:"+c1_branchNotFound+", c1_related:"+c1_related+", c1:"+c1);
			} catch (InterruptedException e) {
				e.printStackTrace();
				return "<===== CATCH FAIL RELATE MD WITH BRANCH =====>";
			} catch (ExecutionException e) {
				e.printStackTrace();
				return "<===== CATCH FAIL RELATE MD WITH BRANCH =====>";
			}
		}
		executor.shutdown();
//logger.debug("EXECUTOR shutdown");


// VERSI : just 1 thread
//		for(MasterData md : masterDatas) {
//			c1++;
//			String theBranch = md.getCIF().getBRANCH();
//
//			// 1. check the branch_code
//			if (StringUtils.isBlank(theBranch)) {													// code is blank
//				// set STATUS = FAIL
//				md.setSTATUS_MD(Constant.STATUS__MD_FAIL);
//				md.setUpdatedBy(user);
//				md.setUpdatedDate(new Date());
//
//				c1_blankCode++;
//				logger.warn("{}. MD[{}] has blank branchCode", c1, md.getSid());						// md.getCIF().getCifSid()
//			} else {																				// code is not blank
//				Branch branch = branchDaoHibernate.getByCode(theBranch);
//
//				// 2. get the branch object
//				if (branch == null) {								// branch was not found
//
//					// set STATUS = FAIL
//					md.setSTATUS_MD(Constant.STATUS__MD_FAIL);
//					md.setUpdatedBy(user);
//					md.setUpdatedDate(new Date());
//
//					c1_branchNotFound++;
//					logger.warn("{}. MD[{}] with Branch[{}] : branch not found", new Object[] {c1, md.getSid(), theBranch});	//md.getCIF().getCifSid()
//				} else {																			// relate branch with MasterData
//
//					// set branch
//					md.setBranch(branch);
//
//					// set STATUS = INIT
//					md.setSTATUS_MD(Constant.STATUS__MD_NPY);
//					md.setUpdatedBy(user);
//					md.setUpdatedDate(new Date());
//
//					// add to TL_MD
//					TL_MD tl_MD = new TL_MD(Constant.ACTION__CREATE, md.getBranch().getCode() + Constant.STRIP + user,
//							md.getSid() + Constant.STRIP + md.getCIF().getCifSid(), Constant.STATUS__MD_NEW + Constant.ARROW_RIGHT + Constant.STATUS__MD_NPY);
//					TL_MD_DaoHibernate.create(tl_MD);
//
//					c1_related++;
//					logger.info("{}. Related MD[{}] with Branch[{}]", new Object[] {c1, md.getCIF().getCifSid(), theBranch});
//				}
//			}
//
//			// update
//			masterDataDaoHibernate.update(md);
//		}

		masterDatas.clear();
		return "<===== SUCCESS RELATE MD WITH BRANCH =====> Total:" + c1 + ", related:" + c1_related + ", fail blank code:" + c1_blankCode+ ", fail branch not found:" + c1_branchNotFound;
	}
	
	
	/*
	 * Fail would be set when :
	 * 1. code is blank
	 * 2. branch was not found
	 */
	@Override
	public String relateWithBranch(final String user) {

		/*
		 * get MasterDate where STATUS=NEW
		 * For STATUS=FAIL init when add branch
		 */

//		SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd");
//	    String date = "2012/10/17";
//	    java.util.Date utilDate = null;
//	    try {
//	      utilDate = formatter.parse(date);
//	    } catch (ParseException e) {
//	      // TODO Auto-generated catch block
//	      e.printStackTrace();
//	    }
	    
		List<MasterData> masterDatas = masterDataDaoHibernate.findByStatuses(Constant.STATUS__MD_NEW, Constant.STATUS__MD_FAIL);
		System.out.println("===============>masterDatas size: "+masterDatas.size());
		
//		List<MasterData> masterDatas = masterDataDaoHibernate.findBySIDStatuses(Constant.STATUS__MD_NEW, Constant.STATUS__MD_FAIL,"12a35869-9f96-47e0-9f10-0b4b788ba235");

//	List<MasterData> masterDatas = masterDataDaoHibernate.findByStatuses(Constant.STATUS__MD_NEW, Constant.STATUS__MD_FAIL, utilDate, utilDate);
//		List<MasterData> masterDatas = masterDataDaoHibernate.findByStatus(Constant.STATUS__MD_NEW);
		logger.info("Found {} MDs", masterDatas.size());

		int c1_blankCode=0;
		int c1_branchNotFound=0;
		int c1_related=0;
		int c1=0;

// VERSI : multithread, but transaction just support 1 connection so time always same

//		List<String> masterDatas = new ArrayList<String>();
//		for (int j=0; j<18; j++) {
//			masterDatas.add(String.valueOf(j));
//		}
//logger.debug("SIZE: "+masterDatas.size());

		int threadInLoop=threadSum;
		int size=masterDatas.size();
		int per=size/threadInLoop;
		int mod=size%threadInLoop;
		executor = Executors.newFixedThreadPool(threadInLoop);
		futures = new ArrayList<Future<HashMap<String, Integer>>>();

		if (mod!=0) {
			threadInLoop--;
			int from = threadInLoop*per;
			int to = size-1;
			Callable<HashMap<String, Integer>> worker = new RelateBranchJob(user, masterDatas, from, to);
	        Future<HashMap<String, Integer>> submit = executor.submit(worker);
	        futures.add(submit);

//	        System.out.println("SIZE: "+size+" thread: "+threadInLoop+ " SISA: "+mod);
//			processCallable(threadInLoop*per, size-1, masterDatas, user);
//			processCallable(threadInLoop*per, size-1, masterDatas);
		}

		for (int i=0; i<threadInLoop; i++) {
			int from=i*per;
			int to=(i+1)*per-1;
			Callable<HashMap<String, Integer>> worker = new RelateBranchJob(user, masterDatas, from, to);
	        Future<HashMap<String, Integer>> submit = executor.submit(worker);
	        futures.add(submit);

//			processCallable(from, to, masterDatas, user);
//			processCallable(from, to, masterDatas);
		}


        // retrieve the result
		HashMap<String, Integer> hasil;
		for (Future<HashMap<String, Integer>> future : futures) {
			try {
				hasil = future.get();
				c1_blankCode += hasil.get("c_blankCode");
				c1_branchNotFound += hasil.get("c_branchNotFound");
				c1_related += hasil.get("c_related");
				c1 += hasil.get("c");
//logger.debug("c1_blankCode:"+c1_blankCode+", c1_branchNotFound:"+c1_branchNotFound+", c1_related:"+c1_related+", c1:"+c1);
			} catch (InterruptedException e) {
				e.printStackTrace();
				return "<===== CATCH FAIL RELATE MD WITH BRANCH =====>";
			} catch (ExecutionException e) {
				e.printStackTrace();
				return "<===== CATCH FAIL RELATE MD WITH BRANCH =====>";
			}
		}
		executor.shutdown();
//logger.debug("EXECUTOR shutdown");


// VERSI : just 1 thread
//		for(MasterData md : masterDatas) {
//			c1++;
//			String theBranch = md.getCIF().getBRANCH();
//
//			// 1. check the branch_code
//			if (StringUtils.isBlank(theBranch)) {													// code is blank
//				// set STATUS = FAIL
//				md.setSTATUS_MD(Constant.STATUS__MD_FAIL);
//				md.setUpdatedBy(user);
//				md.setUpdatedDate(new Date());
//
//				c1_blankCode++;
//				logger.warn("{}. MD[{}] has blank branchCode", c1, md.getSid());						// md.getCIF().getCifSid()
//			} else {																				// code is not blank
//				Branch branch = branchDaoHibernate.getByCode(theBranch);
//
//				// 2. get the branch object
//				if (branch == null) {								// branch was not found
//
//					// set STATUS = FAIL
//					md.setSTATUS_MD(Constant.STATUS__MD_FAIL);
//					md.setUpdatedBy(user);
//					md.setUpdatedDate(new Date());
//
//					c1_branchNotFound++;
//					logger.warn("{}. MD[{}] with Branch[{}] : branch not found", new Object[] {c1, md.getSid(), theBranch});	//md.getCIF().getCifSid()
//				} else {																			// relate branch with MasterData
//
//					// set branch
//					md.setBranch(branch);
//
//					// set STATUS = INIT
//					md.setSTATUS_MD(Constant.STATUS__MD_NPY);
//					md.setUpdatedBy(user);
//					md.setUpdatedDate(new Date());
//
//					// add to TL_MD
//					TL_MD tl_MD = new TL_MD(Constant.ACTION__CREATE, md.getBranch().getCode() + Constant.STRIP + user,
//							md.getSid() + Constant.STRIP + md.getCIF().getCifSid(), Constant.STATUS__MD_NEW + Constant.ARROW_RIGHT + Constant.STATUS__MD_NPY);
//					TL_MD_DaoHibernate.create(tl_MD);
//
//					c1_related++;
//					logger.info("{}. Related MD[{}] with Branch[{}]", new Object[] {c1, md.getCIF().getCifSid(), theBranch});
//				}
//			}
//
//			// update
//			masterDataDaoHibernate.update(md);
//		}

		masterDatas.clear();
		return "<===== SUCCESS RELATE MD WITH BRANCH =====> Total:" + c1 + ", related:" + c1_related + ", fail blank code:" + c1_blankCode+ ", fail branch not found:" + c1_branchNotFound;
	}

//	private void processCallable(final int from, final int to, final List<String> masterDatas) {
	@SuppressWarnings("unused")
	private void processCallable(final int from, final int to, final List<MasterData> masterDatas, final String user) {

		Callable<HashMap<String, Integer>> worker = new Callable<HashMap<String, Integer>>() {

			@Override
			public HashMap<String, Integer> call() throws Exception {
				int c_blankCode=0;
				int c_branchNotFound=0;
				int c_related=0;
				int c=0;
				HashMap<String, Integer> result = new HashMap<String, Integer>();

				for (int i=from; i<to+1; i++) {
//					System.out.println("MD "+masterDatas.get(i));
//					Thread.sleep(1000);

					c++;
					MasterData md = masterDatas.get(i);
					String theBranch = md.getCIF().getBRANCH();

					//add by Ozy
					List<Account> lAccount = md.getAccounts();
					Account act= null;
					if (lAccount.size()!=0){
						 act = lAccount.get(0);
					}


					// 1. check the branch_code in M_CIF kalo is null or blank?
					if (StringUtils.isBlank(theBranch)||theBranch.isEmpty()||(null==theBranch)||theBranch.equalsIgnoreCase("NULL")) {// code is blank
						logger.info("== EMPTY ==> branch code: "+theBranch+ "account :"+lAccount.size());
						//2. check branch_code di M_ACCOUNT
						if (act != null && act.getBRANCH() !=null ){
							Branch branch = branchDaoHibernate.getByCode(act.getBRANCH());
							// 2. get the branch object
							if (branch == null) {								// branch was not found

								// set STATUS = FAIL
								md.setSTATUS_MD(Constant.STATUS__MD_FAIL);
								md.setUpdatedBy(user);
								md.setUpdatedDate(new Date());

								c_branchNotFound++;
								logger.warn("{}. MD[{}] with Branch[{}] : branch not found", new Object[] {c, md.getSid(), theBranch});	//md.getCIF().getCifSid()
								logger.info("get M_ACCOUNT code_branch {} with Branch[{}]", act.getBRANCH(), theBranch );
								logger.info("===> gagal account branch_code: "+act.getBRANCH());
							} else {																			// relate branch with MasterData
								// set branch
								md.setBranch(branch);

								// set STATUS = INIT
								md.setSTATUS_MD(Constant.STATUS__MD_NPY);
								md.setUpdatedBy(user);
								md.setUpdatedDate(new Date());

								// add to TL_MD
								TL_MD tl_MD = new TL_MD(Constant.ACTION__CREATE, md.getBranch().getCode() + Constant.STRIP + user,
										md.getSid() + Constant.STRIP + md.getCIF().getCifSid(), Constant.STATUS__MD_NEW + Constant.ARROW_RIGHT + Constant.STATUS__MD_NPY);
								TL_MD_DaoHibernate.create(tl_MD);

								c_related++;
								logger.info("{}. Related MD[{}] with Branch[{}]", new Object[] {c, md.getCIF().getCifSid(), theBranch});
								logger.info("{}. Related MD[{}] with Branch[{}]", new Object[] {c, md.getCIF().getCifSid(), branch.getName()});
								logger.info("=====set branch berhasil account branch_code: "+act.getBRANCH());
							}

						}else{ // jika tidak ketemu di M_ACCOUNT --> set STATUS = FAIL
							md.setSTATUS_MD(Constant.STATUS__MD_FAIL);
							md.setUpdatedBy(user);
							md.setUpdatedDate(new Date());

							c_blankCode++;
							logger.warn("{}. MD[{}] has blank branchCode", c, md.getSid());						// md.getCIF().getCifSid()
							logger.info("not found in M_ACCOUNT size {}.  ", lAccount.size());
						}
					} else {	// code is not blank
						logger.info("== block branch code not empty ==> "+theBranch);
						Branch branch = branchDaoHibernate.getByCode(theBranch);

						// 2. get the branch object
						if (branch == null) {								// branch was not found

							// set STATUS = FAIL
							md.setSTATUS_MD(Constant.STATUS__MD_FAIL);
							md.setUpdatedBy(user);
							md.setUpdatedDate(new Date());

							c_branchNotFound++;
							logger.warn("{}. MD[{}] with Branch[{}] : branch not found", new Object[] {c, md.getSid(), theBranch});	//md.getCIF().getCifSid()
						} else {																			// relate branch with MasterData

							// set branch
							md.setBranch(branch);

							// set STATUS = INIT
							md.setSTATUS_MD(Constant.STATUS__MD_NPY);
							md.setUpdatedBy(user);
							md.setUpdatedDate(new Date());

							// add to TL_MD
							TL_MD tl_MD = new TL_MD(Constant.ACTION__CREATE, md.getBranch().getCode() + Constant.STRIP + user,
									md.getSid() + Constant.STRIP + md.getCIF().getCifSid(), Constant.STATUS__MD_NEW + Constant.ARROW_RIGHT + Constant.STATUS__MD_NPY);
							TL_MD_DaoHibernate.create(tl_MD);

							c_related++;
							logger.info("{}. Related MD[{}] with Branch[{}]", new Object[] {c, md.getCIF().getCifSid(), theBranch});
						}
					}

					masterDataDaoHibernate.merge(md);
				}

				result.put("c_blankCode", c_blankCode);
				result.put("c_branchNotFound", c_branchNotFound);
				result.put("c_related", c_related);
				result.put("c", c);
				return result;
			}
		};

//System.out.println("START :"+from+" - "+to);
        Future<HashMap<String, Integer>> submit = executor.submit(worker);
        futures.add(submit);
	}

	/*
	 * PURPOSE :
	 * - update STATUS_MD
	 * - set SLA_BEGIN_DATE if status=RECEIVED (completing docs)
	 */
	@Override
	public int updateStatuses(String[] sids, String status) {
		System.out.println("updateStatuses");
		int count = 0;
		if ((sids != null) && (sids.length > 0)) {

			// convert to set agar tidak dulikasi update
			Set<String> sidsSet = new HashSet<String>();
			for (int i=0; i<sids.length; i++) {
				sidsSet.add(sids[i]);
			}

			// update
			for (String sid : sidsSet) {
				updateStatus(sid, status);
				count++;
			}
		}
		return count;
	}

	@Override
	public void updateStatus(String sid, String status) {

		String username = SecurityContextHolder.getContext().getAuthentication().getName();
		updateStatus(sid, status, username);
	}

	@Override
	public void updateStatus(String sid, String status, String username) {
		System.out.println("---update status --");
		MasterData md = masterDataDaoHibernate.get(sid);
		System.out.println("md: "+md.getSTATUS_MD());
		if ((md!=null) && (md.getSTATUS_MD()!=status)) {		// jika md ga null & terjadi perubahan status
			String oldStatus = md.getSTATUS_MD();

			// if status=RECEIVED (perhitungan SLA started)
			if (status.equals(Constant.STATUS__MD_RECEIVED)) {
				md.setBeginDate(new Date());
			}

			md.setSTATUS_MD(status);
			md.setUpdatedBy(username);
			md.setUpdatedDate(new Date());

			// status
			// NOTE: untuk status TBO, diset pada saat email berhasil terkirim
			if (status.equals(Constant.STATUS__MD_RECEIVED)) {
				md.setRECEIVED_DATE(new Date());
				md.setRECEIVED_USER(username);
			} else if (status.equals(Constant.STATUS__MD_COMPLETED)) {
				md.setPROGRESS_DATE(new Date());
				md.setPROGRESS_USER(username);
			} else if (status.equals(Constant.STATUS__MD_WAIT4APP)) {
				md.setWAIT_APPROVAL_DATE(new Date());
				md.setWAIT_APPROVAL_USER(username);
			} else if (status.equals(Constant.STATUS__MD_APPROVED)) {
				md.setAPPROVED_DATE(new Date());
				md.setAPPROVED_USER(username);
			}

			// log it by user
			// klo ada error di bawah, itu mungkin error branchCode
//			System.out.println(md.getCIF().getCifSid());
//			System.out.println(md.getBranch().getCode());
			TL_MD tl_MD = new TL_MD(Constant.ACTION__UPDATE, md.getBranch().getCode() + Constant.STRIP + username,
					md.getSid() + Constant.STRIP + md.getCIF().getCifSid(), oldStatus + Constant.ARROW_RIGHT + md.getSTATUS_MD());
			TL_MD_DaoHibernate.create(tl_MD);
		}
	}

	/*
	 * PURPOSE : next step after user decide TBO or PROGRESS
	 */
	@Override
	public void decideCompleted(String[] mdSids, String status, String emailMsg, String user) {
		if ((mdSids != null) && (mdSids.length > 0)) {

			if (status.equals(Constant.STATUS__MD_TBO)) {

				// convert to set agar tidak dulikasi update
				Set<String> mdSidsSet = new HashSet<String>();
				for (int i=0; i<mdSids.length; i++) {
					mdSidsSet.add(mdSids[i]);
				}

				// send mails, include update status to TBO
				for (String mdSid : mdSidsSet) {
					sendEmailToBranch(mdSid, emailMsg, user);
				}

			} else if (status.equals(Constant.STATUS__MD_COMPLETED)) {

				// update status to PROGRESS
				updateStatuses(mdSids, status);

				// validate
//				validationProduct.validateProducts(mdSids);
			}
		}
	}

	/* Many data
	 * PURPOSE : next step after user decide APPROVED or REJECT
	 */
	@Override
	public void decideApproved(String[] mdSids, String status) {

		if ((mdSids != null) && (mdSids.length > 0)) {

			if (status.equals(Constant.STATUS__MD_COMPLETED)) {

				// update status to REJECTED
				updateStatuses(mdSids, status);

			} else if (status.equals(Constant.STATUS__MD_APPROVED)) {

				// update status to APPROVED
				updateStatuses(mdSids, status);
			}
		}
	}

	/* One Data
	 * PURPOSE : next step after user decide APPROVED or REJECT
	 */
	@Override
	public void decideApproved(String mdSid, String status) {

		if (status.equals(Constant.STATUS__MD_COMPLETED)) {

			// update status to REJECTED
			updateStatus(mdSid, status);

		} else if (status.equals(Constant.STATUS__MD_APPROVED)) {

			// update status to APPROVED
			updateStatus(mdSid, status);
		}
	}

	/*
	 * PART I	: send email to branch
	 * PART II	: update status to TBO
	 */
	@Override
	public String sendEmailToBranch(String mdSid, String emailMsg, String user) {

		// get user from email
		User userObject = userDaoHibernate.get(user);

		// get masterData based on masterId
		MasterData md = masterDataDaoHibernate.get(mdSid);

		// PART I	: send email to branch
		String result = branchEmailSender.sendMailToBranchUser(
				md.getCIF().getCifSid(),
				md.getCIF().getCUSTOMER_TYPE(),
				userObject.getEmail(),
				md.getBranch().getEmail(),	// kirim ke branch based account not cif
				emailMsg);
//		String result = Constant.STATUS__SUCCESS;
//		if (result.equals(Constant.STATUS__SUCCESS)) {
			String oldStatus = md.getSTATUS_MD();

			// PART II	: update status to TBO
			md.setSTATUS_MD(Constant.STATUS__MD_TBO);
			md.setTBO_DATE(new Date());
			md.setTBO_USER(user);
			//update md
			masterDataDaoHibernate.merge(md);
			// log it by user (jika pada TL_MD sudah ada maka status seperti '-3 -> -3')
			TL_MD tl_MD = new TL_MD(Constant.ACTION__UPDATE, md.getBranch().getCode() + Constant.STRIP + user,
					md.getSid() + Constant.STRIP + md.getCIF().getCifSid(), oldStatus + Constant.ARROW_RIGHT + md.getSTATUS_MD());
			TL_MD_DaoHibernate.create(tl_MD);

//		} else {
//			// fail to send email
//		}

		return result;
	}

	/*
	 * 1. set endDate for MasterData
	 * 2. calculate grade : compare PARAM_KEY with selisih waktu
	 * 3. update STATUS_MD to WAIT4APP
	 */
	@Override
	public void calculateGrade(MasterData md) {

		// 1.
		md.setEndDate(new Date());

		Calendar beginCal = Calendar.getInstance(); 	//new GregorianCalendar();
        Calendar endCal = Calendar.getInstance(); 		//new GregorianCalendar();
        beginCal.setTime(md.getBeginDate());
        endCal.setTime(md.getEndDate());

        // 2. calculate, the grade & set status
        Long value = (endCal.getTime().getTime() - beginCal.getTime().getTime()) / (24 * 3600 * 1000);
        Long slaParam = Long.parseLong(parameterBeans.getSLAparam().toString());
        md.setGradeValue(slaParam-value);

        if (value > slaParam) {									// out of date
        	md.setSTATUS_SLA(Constant.STATUS__OUT_OF_DATE);
        } else {
        	md.setSTATUS_SLA(Constant.STATUS__ON_TIME);
        }
        masterDataDaoHibernate.update(md);

        // 3.
        updateStatus(md.getSid(), Constant.STATUS__MD_WAIT4APP);
	}

	@Override
	public String decideValidated(MasterData md, String msg) {

		// set custody yg melengkapi data
		md.setLAST_CHECK_USER(SecurityContextHolder.getContext().getAuthentication().getName());
		md.setSTATUS_MD_MSG(msg);

		if (msg.length() > 0) {											// *UNCOMPLETE*

			// set STATUS_MD_MSG for MasterData
			// STATUS_MD still PROGRESS, PROGRESS status be updated
//			md.setPROGRESS_DATE(new Date());
//			md.setPROGRESS_USER(SecurityContextHolder.getContext().getAuthentication().getName());

			masterDataDaoHibernate.update(md);
			return "data berhasil tersimpan";
		} else {														// *COMPLETE*
			calculateGrade(md);
			return "data berhasil tersimpan dan menunggu proses approval";
		}
	}

	@Override
	public List<Report01Model> findReport01(Long branchId, Date from, Date to) {

		List<Report01Model> results = new ArrayList<Report01Model>();
		User user = userDaoHibernate.get(SecurityContextHolder.getContext().getAuthentication().getName());

		if (branchId==-1) {																// -1 : all branch
			for (Account a : accountDaoHibernate.findAllByDateRange(from, to)) {
				results.add(new Report01Model(
						a.getMasterData().getCIF().getCifSid(),
						a.getMasterData().getCIF().getCUST_FULL_NAME(),
						a.getACCOUNT(),
						a.getACCOUNT_OPEN(),
						a.getCREATED_USER_ID(),
						a.getACCOUNT_TYPE(),
						a.getMasterData().getBranch().getCode()));
			}
		} else if (branchId==-2) {														// -2 -> all branch in area
			for (Account a : accountDaoHibernate.findByAreaDateRange(user.getArea().getId(), from, to)) {
				results.add(new Report01Model(
						a.getMasterData().getCIF().getCifSid(),
						a.getMasterData().getCIF().getCUST_FULL_NAME(),
						a.getACCOUNT(),
						a.getACCOUNT_OPEN(),
						a.getCREATED_USER_ID(),
						a.getACCOUNT_TYPE(),
						a.getMasterData().getBranch().getCode()));
			}
		} else {																		// branch
			for (Account a : accountDaoHibernate.findByBranchDateRange(branchId, from, to)) {
				results.add(new Report01Model(
						a.getMasterData().getCIF().getCifSid(),
						a.getMasterData().getCIF().getCUST_FULL_NAME(),
						a.getACCOUNT(),
						a.getACCOUNT_OPEN(),
						a.getCREATED_USER_ID(),
						a.getACCOUNT_TYPE(),
						a.getMasterData().getBranch().getCode()));
			}
		}
		return results;
	}

	// for user area NOT jakarta
	@Override
	public List<Report04Model> findReport04(Long branchId, Date from, Date to) {

		List<Report04Model> results = new ArrayList<Report04Model>();
		User user = userDaoHibernate.get(SecurityContextHolder.getContext().getAuthentication().getName());

		if (branchId==-1) {																// -1 : all branch
			for (Account a : accountDaoHibernate.findAllCloseByDateRange(from, to)) {
				results.add(new Report04Model(
						a.getMasterData().getBranch().getCode(),
						a.getMasterData().getCIF().getCifSid(),
						a.getACCOUNT(),
						a.getMasterData().getCIF().getCUST_FULL_NAME(),
						a.getACCOUNT_TYPE(),
						a.getACCOUNT_CLOSE()));
			}
		} else if (branchId==-2) {														// -2 -> all branch in area
			for (Account a : accountDaoHibernate.findCloseByAreaDateRange(user.getArea().getId(), from, to)) {
				results.add(new Report04Model(
						a.getMasterData().getBranch().getCode(),
						a.getMasterData().getCIF().getCifSid(),
						a.getACCOUNT(),
						a.getMasterData().getCIF().getCUST_FULL_NAME(),
						a.getACCOUNT_TYPE(),
						a.getACCOUNT_CLOSE()));
			}
		} else {																		// branch
			for (Account a : accountDaoHibernate.findCloseByBranchDateRange(branchId, from, to)) {
				results.add(new Report04Model(
						a.getMasterData().getBranch().getCode(),
						a.getMasterData().getCIF().getCifSid(),
						a.getACCOUNT(),
						a.getMasterData().getCIF().getCUST_FULL_NAME(),
						a.getACCOUNT_TYPE(),
						a.getACCOUNT_CLOSE()));
			}
		}
		return results;
	}

	@Override
	public List<Report05Model> findReport05(Long branchId, Date from, Date to) {

		List<Report05Model> results = new ArrayList<Report05Model>();
		User user = userDaoHibernate.get(SecurityContextHolder.getContext().getAuthentication().getName());

		if (branchId==-1) {																// -1 : all branch
			for (MasterData md : masterDataDaoHibernate.findAllByDateSend(Constant.STATUS__MD_RESPONSED, Constant.STATUS__MD_FAIL_UPLOAD, from, to)) {
				results.add(new Report05Model(
						md.getBranch().getCode(),
						md.getCIF().getCifSid(),
						md.getCIF().getCUST_FULL_NAME(),
						md.getSTATUS_MD(),
						md.getFAIL_UPLOAD_REASON()));
			}
		} else if (branchId==-2) {														// -2 -> all branch in area
			for (MasterData md : masterDataDaoHibernate.findByAreaIdStatusesDate(user.getArea().getId(), Constant.STATUS__MD_RESPONSED, Constant.STATUS__MD_FAIL_UPLOAD, from, to)) {
				results.add(new Report05Model(
						md.getBranch().getCode(),
						md.getCIF().getCifSid(),
						md.getCIF().getCUST_FULL_NAME(),
						md.getSTATUS_MD(),
						md.getFAIL_UPLOAD_REASON()));
			}
		} else {																		// branch
			for (MasterData md : masterDataDaoHibernate.findByBranchIdStatusesDate(branchId, Constant.STATUS__MD_RESPONSED, Constant.STATUS__MD_FAIL_UPLOAD, from, to)) {
				results.add(new Report05Model(
						md.getBranch().getCode(),
						md.getCIF().getCifSid(),
						md.getCIF().getCUST_FULL_NAME(),
						md.getSTATUS_MD(),
						md.getFAIL_UPLOAD_REASON()));
			}
		}
		return results;
	}
}
